
const threshold = {
    hue: 110,
    tolerance: 60,
    saturation: 30,
}

// RGB转HSL辅助函数
export const rgbToHsl = (r: number, g: number, b: number) => {
    r /= 255;
    g /= 255;
    b /= 255;

    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    let h = 0,
        s,
        l = (max + min) / 2;

    if (max === min) {
        h = s = 0;
    } else {
        const d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch (max) {
            case r:
                h = (g - b) / d + (g < b ? 6 : 0);
                break;
            case g:
                h = (b - r) / d + 2;
                break;
            case b:
                h = (r - g) / d + 4;
                break;
        }
        h /= 6;
    }

    return [h, s, l];
};

// 去除背景
export const processChromaKey = (
    remoteVideoRef: HTMLVideoElement,
    canvasRef: HTMLCanvasElement,
): void => {
    if (!remoteVideoRef || !canvasRef) return;

    // 优先使用 WebGL (GPU)
    const stateKey = '__ckgl_state__';
    type CKGLState = {
        gl: WebGLRenderingContext;
        program: WebGLProgram;
        positionBuffer: WebGLBuffer;
        texcoordBuffer: WebGLBuffer;
        texture: WebGLTexture;
        a_position: number;
        a_texCoord: number;
        u_resolution: WebGLUniformLocation | null;
        u_keyColor: WebGLUniformLocation | null;
        u_threshold: WebGLUniformLocation | null;
        u_smoothness: WebGLUniformLocation | null;
        u_despill: WebGLUniformLocation | null;
        u_despillStrength: WebGLUniformLocation | null;
        u_matteOffset: WebGLUniformLocation | null;
        u_texelSize: WebGLUniformLocation | null;
        u_edgeFeather: WebGLUniformLocation | null;
        u_erode: WebGLUniformLocation | null;
        rafId?: number;
        useVFC?: boolean;
        // 新增：当前绑定的视频元素与其 VFC 回调 ID
        video?: HTMLVideoElement;
        vfcId?: number;
    };

    const initWebGL = (canvas: HTMLCanvasElement): CKGLState | null => {
        const gl = (canvas.getContext('webgl', { premultipliedAlpha: false, preserveDrawingBuffer: false, antialias: false, powerPreference: 'high-performance' as any })
            || canvas.getContext('experimental-webgl')) as WebGLRenderingContext | null;
        if (!gl) return null;

        const vsSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
    gl_Position = vec4(a_position, 0.0, 1.0);
    v_texCoord = a_texCoord;
}`;
        const fsSource = `
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_video;
uniform vec3 u_keyColor;
uniform float u_threshold;
uniform float u_smoothness;
uniform float u_despill;
uniform float u_despillStrength;
uniform float u_matteOffset;
void main() {
    vec4 color = texture2D(u_video, v_texCoord);
    float dist = distance(color.rgb, u_keyColor);
    float edge0 = u_threshold - u_smoothness;
    float edge1 = u_threshold + u_smoothness;
    float alpha = smoothstep(edge0, edge1, dist);
    // 额外：基于绿色过量，放大边缘附近的透明度
    float greenEx = max(0.0, color.g - max(color.r, color.b));
    float soft = smoothstep(0.02, 0.18, greenEx);
    alpha = clamp((alpha - u_matteOffset) / (1.0 - u_matteOffset), 0.0, 1.0);
    float Y = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
    float dark = 1.0 - smoothstep(0.12, 0.35, Y);
    float softScale = mix(0.35, 0.6, dark);
    alpha = clamp(alpha - soft * softScale, 0.0, 1.0);
    vec3 rgb = color.rgb;
    float spill = (1.0 - alpha) * u_despill;
    float avgRB = (rgb.r + rgb.b) * 0.5;
    rgb.g = mix(rgb.g, avgRB, clamp(spill * u_despillStrength, 0.0, 1.0));
    gl_FragColor = vec4(rgb, alpha);
}`;

        const compile = (type: number, src: string) => {
            const shader = gl.createShader(type)!;
            gl.shaderSource(shader, src);
            gl.compileShader(shader);
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                gl.deleteShader(shader);
                return null as any;
            }
            return shader;
        };

        const vs = compile(gl.VERTEX_SHADER, vsSource);
        const fs = compile(gl.FRAGMENT_SHADER, fsSource);
        if (!vs || !fs) return null;

        const program = gl.createProgram()!;
        gl.attachShader(program, vs);
        gl.attachShader(program, fs);
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            return null;
        }
        gl.useProgram(program);

        const positionBuffer = gl.createBuffer()!;
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        const quad = new Float32Array([
            -1, -1,
            1, -1,
            -1, 1,
            -1, 1,
            1, -1,
            1, 1,
        ]);
        gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);

        const texcoordBuffer = gl.createBuffer()!;
        gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
        const uv = new Float32Array([
            0, 0,
            1, 0,
            0, 1,
            0, 1,
            1, 0,
            1, 1,
        ]);
        gl.bufferData(gl.ARRAY_BUFFER, uv, gl.STATIC_DRAW);

        const texture = gl.createTexture()!;
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

        const a_position = gl.getAttribLocation(program, 'a_position');
        const a_texCoord = gl.getAttribLocation(program, 'a_texCoord');
        const u_resolution = gl.getUniformLocation(program, 'u_resolution');
        const u_keyColor = gl.getUniformLocation(program, 'u_keyColor');
        const u_threshold = gl.getUniformLocation(program, 'u_threshold');
        const u_smoothness = gl.getUniformLocation(program, 'u_smoothness');
        const u_despill = gl.getUniformLocation(program, 'u_despill');
        const u_despillStrength = gl.getUniformLocation(program, 'u_despillStrength');
        const u_matteOffset = gl.getUniformLocation(program, 'u_matteOffset');

        gl.enableVertexAttribArray(a_position);
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);

        gl.enableVertexAttribArray(a_texCoord);
        gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
        gl.vertexAttribPointer(a_texCoord, 2, gl.FLOAT, false, 0, 0);

        gl.uniform3f(u_keyColor, 0.0, 1.0, 0.0);
        gl.uniform1f(u_threshold, 0.45);
        gl.uniform1f(u_smoothness, 0.12);
        if (u_despill) gl.uniform1f(u_despill, 1.0);
        if (u_despillStrength) gl.uniform1f(u_despillStrength, 0.85);
        if (u_matteOffset) gl.uniform1f(u_matteOffset, 0.08);

        return {
            gl,
            program,
            positionBuffer,
            texcoordBuffer,
            texture,
            a_position,
            a_texCoord,
            u_resolution,
            u_keyColor,
            u_threshold,
            u_smoothness,
        } as CKGLState;
    };

    const drawWithWebGL = (state: CKGLState) => {
        const gl = state.gl;
        const video = state.video!;
        if (!video) return;
        if (video.videoWidth === 0 || video.videoHeight === 0) return;
        if (canvasRef.width !== video.videoWidth || canvasRef.height !== video.videoHeight) {
            canvasRef.width = video.videoWidth;
            canvasRef.height = video.videoHeight;
            gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
        }

        gl.useProgram(state.program);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, state.texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
        try {
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
        } catch { }
        gl.drawArrays(gl.TRIANGLES, 0, 6);
    };

    const scheduleNext = (state: CKGLState) => {
        const video = state.video!;
        const vfc: any = (video as any).requestVideoFrameCallback;
        if (typeof vfc === 'function') {
            state.useVFC = true;
            state.vfcId = vfc.call(video, () => {
                drawWithWebGL(state);
                scheduleNext(state);
            });
        } else {
            state.rafId = requestAnimationFrame(() => {
                drawWithWebGL(state);
                scheduleNext(state);
            });
        }
    };

    let state = (canvasRef as any)[stateKey] as CKGLState | undefined;
    if (!state) {
        state = initWebGL(canvasRef) || undefined;
        if (state) {
            (canvasRef as any)[stateKey] = state;
            // 首次绑定：设置视频并开始调度
            state.video = remoteVideoRef;
            scheduleNext(state);
            return;
        }
    } else {
        // 已初始化：切换到新的 video，取消旧的回调并重新调度
        if (state.useVFC) {
            const cancelVFC = (state.video as any)?.cancelVideoFrameCallback;
            if (typeof cancelVFC === 'function' && state.vfcId) {
                try { cancelVFC.call(state.video, state.vfcId); } catch { }
            }
        } else if (state.rafId) {
            cancelAnimationFrame(state.rafId);
            state.rafId = undefined;
        }
        state.video = remoteVideoRef;
        scheduleNext(state);
        return;
    }

    // 回退到 CPU 方案（仅在 WebGL 不可用时）
    const ctx = canvasRef.getContext("2d");
    if (!ctx) return;

    canvasRef.width = remoteVideoRef.videoWidth;
    canvasRef.height = remoteVideoRef.videoHeight;

    ctx.drawImage(remoteVideoRef, 0, 0);

    const imageData = ctx.getImageData(0, 0, canvasRef.width, canvasRef.height);
    const pixels = imageData.data;

    for (let i = 0; i < pixels.length; i += 4) {
        const r = pixels[i];
        const g = pixels[i + 1];
        const b = pixels[i + 2];

        const hsl = rgbToHsl(r, g, b);
        const h = hsl[0] * 360;
        const s = hsl[1] * 100;
        const l = hsl[2] * 100;

        if (
            g > Math.max(r, b) &&
            Math.abs(h - threshold.hue) < threshold.tolerance &&
            s > threshold.saturation &&
            l > 10 &&
            l < 90
        ) {
            const hueDiff = Math.abs(h - threshold.hue);
            const alpha = Math.min(
                1,
                (hueDiff / threshold.tolerance) *
                (1 - (s - threshold.saturation) / (100 - threshold.saturation))
            );

            pixels[i + 3] = Math.floor(255 * alpha);
        }
    }

    ctx.putImageData(imageData, 0, 0);
    requestAnimationFrame(() => processChromaKey(remoteVideoRef, canvasRef));
};

